﻿using ImageK.Gui;
using ImageK.Measure;
using ImageK.Plugin.Filter;
using ImageK.Process;
using ImageK.Util;
using System;
using System.Collections.Generic;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Windows.Forms.Design.AxImporter;

namespace ImageK.Plugin
{
    /** Implements the Image/Stacks/Plot Z-axis Profile command, 
        which plots the selection mean gray value versus slice number.
    */
    public class ZAxisProfiler : Measurements, PlugIn, PlotMaker 
    {
	    private static string[] choices = { "time", "z-axis" };
        private static string choice = choices[0];
        private bool showingDialog;
        private ImagePlus imp;
        private bool isPlotMaker;
        private bool timeProfile;
        private bool firstTime = true;
        private string options;

        /** Returns a Plot of the selection mean gray value versus slice number. */
        public static Plot getPlot(ImagePlus imp)
        {
            return getPlot(imp, "time");
        }

        /** Returns a Plot of the selection mean versus slice number for the
		    specified hyperstack, where 'options' can be "time" or "z-axis". */
        public static Plot getPlot(ImagePlus imp, string options)
        {
            ZAxisProfiler zap = new ZAxisProfiler();
            zap.imp = imp;
            zap.options = options;
            zap.isPlotMaker = true;
            Plot plot = zap.getPlot();
            return plot;
        }

        public void run(string arg)
        {
            imp = IJ.getImage();
            if (imp.getStackSize()<2)
            {
                IJ.error("ZAxisProfiler", "This command requires a stack.");
                return;
            }
            isPlotMaker = true;
            Plot plot = getPlot();
            if (plot!=null)
            {
                if (isPlotMaker)
                    plot.setPlotMaker(this);
                plot.show();
            }
        }

        public Plot getPlot()
        {
            Roi roi = imp.getRoi();
            ImageProcessor ip = imp.getProcessor();
            double minThreshold = ip.getMinThreshold();
            double maxThreshold = ip.getMaxThreshold();
            float[] y;
            bool hyperstack = imp.isHyperStack();
            if (hyperstack)
                y = getHyperstackProfile(roi, minThreshold, maxThreshold);
            else
                y = getZAxisProfile(roi, minThreshold, maxThreshold);
            if (y==null)
                return null;
            float[] x = new float[y.Length];

            string xAxisLabel = showingDialog&&choice.Equals(choices[0]) ? "Frame" : "Slice";
            Calibration cal = imp.getCalibration();
            double calFactor = 1.0;
            double origin = -1;
            if (cal.scaled())
            {
                if (timeProfile)
                {
                    calFactor = (float)cal.frameInterval;
                    bool zeroInterval = calFactor==0;
                    if (zeroInterval)
                        calFactor = 1;
                    else
                        origin = 0;
                    String timeUnit = zeroInterval ? "Frame" : "["+cal.getTimeUnit()+"]";
                    xAxisLabel = timeUnit;
                }
                else
                {
                    calFactor = (float)cal.pixelDepth;
                    bool zeroDepth = calFactor==0;
                    if (zeroDepth)
                        calFactor = 1;
                    else
                        origin = cal.zOrigin;
                    String depthUnit = zeroDepth ? "Slice" : "["+cal.getZUnit()+"]";
                    xAxisLabel = depthUnit;
                }
            }
            for (int i = 0; i<x.Length; i++)
                x[i] = (float)((i-origin)*calFactor);

            string title;
            if (roi!=null)
            {
                Rectangle r = roi.getBounds();
                title = imp.getTitle()+"-"+r.X+"-"+r.Y;
            }
            else
                title = imp.getTitle()+"-0-0";
            //String xAxisLabel = showingDialog&&choice.equals(choices[0])?"Frame":"Slice";
            Plot plot = new Plot(title, xAxisLabel, "Mean", x, y);
            bool useConnectedCircles = x.Length<=60;     //not LINE but CONNECTED_CIRCLES
            if (useConnectedCircles)
                plot.setStyle(0, "black, gray, 1, connected");
            plot.setColor(Color.Black);
            double ymin = ProfilePlot.getFixedMin();
            double ymax = ProfilePlot.getFixedMax();
            if (!(ymin==0.0 && ymax==0.0))
            {
                double[] a = Tools.getMinMax(x);
                double xmin = a[0]; double xmax = a[1];
                plot.setLimits(xmin, xmax, ymin, ymax);
            }
            else
            {
                double[] a = Tools.getMinMax(y);
                ymin = a[0]; ymax = a[1];
            }
            //draw a blue vertical line for the current stack position (in live mode)
            int pos = imp.getCurrentSlice();
            if (hyperstack)
            {
                if (timeProfile)
                    pos = imp.getT();
                else
                    pos = imp.getZ();
            }
            double xx = (pos - 1 - origin)*calFactor;
            if (!useConnectedCircles)
            {  //For a line plot, the frame starts and ends at the first and last point
                if (pos==1)              //shift 1 pxl towards the center to avoid hiding the blue line hidden behind the frame
                    xx += calFactor*Math.Min(0.4, x.Length*0.7/(double)plot.getDrawingFrame().Width);
                else if (pos==x.Length)
                    xx -= calFactor*Math.Min(0.4, x.Length*0.7/(double)plot.getDrawingFrame().Width);
            }
            if (firstTime)               //don't draw the line for the first time, create an (invisible) PlotObject
                xx = Double.NaN;         //(otherwise, the number of PlotObjects would change, breaking the Plot.COPY_EXTRA_OBJECTS)
            plot.setColor(Color.Blue);
            plot.drawLine(xx, ymin-10*(ymax-ymin), xx, ymax+10*(ymax-ymin));
            plot.setColor(Color.Black);
            plot.setLineWidth(1);
            firstTime = false;
            return plot;
        }

        public ImagePlus getSourceImage()
        {
            return imp;
        }

        private float[] getHyperstackProfile(Roi roi, double minThreshold, double maxThreshold)
        {
            int slices = imp.getNSlices();
            int frames = imp.getNFrames();
            int c = imp.getC();
            int z = imp.getZ();
            int t = imp.getT();
            int size = slices;
            if (firstTime)
                timeProfile = slices==1 && frames>1;
            if (options==null && slices>1 && frames>1 && (!isPlotMaker ||firstTime))
            {
                showingDialog = true;
                GenericDialog gd = new GenericDialog("Profiler");
                gd.addChoice("Profile", choices, choice);
                gd.showDialog();
                if (gd.wasCanceled())
                    return null;
                choice = gd.getNextChoice();
                timeProfile = choice.Equals(choices[0]);
            }
            if (options!=null)
                timeProfile = frames>1 && !options.Contains("z");
            if (timeProfile)
                size = frames;
            else
                size = slices;
            float[] values = new float[size];
            Calibration cal = imp.getCalibration();
            ResultsTable rt = new ResultsTable();
            Analyzer analyzer = new Analyzer(imp, rt);
            int measurements = Analyzer.getMeasurements();
            bool showResults = !isPlotMaker && measurements!=0 && measurements!=LIMIT;
            measurements |= MEAN;
            if (showResults)
            {
                if (!Analyzer.resetCounter())
                    return null;
            }
            ImageStack stack = imp.getStack();
            bool showProgress = size>400 || stack.isVirtual();
            for (int i = 1; i<=size; i++)
            {
                if (showProgress)
                    IJ.showProgress(i, size);
                int index = 1;
                if (timeProfile)
                    index = imp.getStackIndex(c, z, i);
                else
                    index = imp.getStackIndex(c, i, t);
                ImageProcessor ip = stack.getProcessor(index);
                if (minThreshold!=ImageProcessor.NO_THRESHOLD)
                    ip.setThreshold(minThreshold, maxThreshold, ImageProcessor.NO_LUT_UPDATE);
                ip.setRoi(roi);
                ImageStatistics stats = ImageStatistics.getStatistics(ip, measurements, cal);
                analyzer.saveResults(stats, roi);
                values[i-1] = (float)stats.mean;
            }
            if (showResults)
                rt.show("Results");
            return values;
        }

        private float[] getZAxisProfile(Roi roi, double minThreshold, double maxThreshold)
        {
            ImageStack stack = imp.getStack();
            if (firstTime)
            {
                int slices = imp.getNSlices();
                int frames = imp.getNFrames();
                timeProfile = slices==1 && frames>1;
            }
            int size = stack.size();
            bool showProgress = size>400 || stack.isVirtual();
            float[] values = new float[size];
            Calibration cal = imp.getCalibration();
            ResultsTable rt = new ResultsTable();
            Analyzer analyzer = new Analyzer(imp, rt);
            int measurements = Analyzer.getMeasurements();
            bool showResults = !isPlotMaker && measurements!=0 && measurements!=LIMIT;
            bool showingLabels = firstTime && showResults && ((measurements&LABELS)!=0 || (measurements&SLICE)!=0);
            measurements |= MEAN;
            if (showResults)
            {
                if (!Analyzer.resetCounter())
                    return null;
            }
            bool isLine = roi!=null && roi.isLine();
            int current = imp.getCurrentSlice();
            for (int i = 1; i<=size; i++)
            {
                if (showProgress)
                    IJ.showProgress(i, size);
                if (showingLabels)
                    imp.setSlice(i);
                ImageProcessor ip = stack.getProcessor(i);
                if (minThreshold!=ImageProcessor.NO_THRESHOLD)
                    ip.setThreshold(minThreshold, maxThreshold, ImageProcessor.NO_LUT_UPDATE);
                ip.setRoi(roi);
                ImageStatistics stats = null;
                if (isLine)
                    stats = getLineStatistics(roi, ip, measurements, cal);
                else
                    stats = ImageStatistics.getStatistics(ip, measurements, cal);
                analyzer.saveResults(stats, roi);
                values[i-1] = (float)stats.mean;
            }
            if (showResults)
                rt.show("Results");
            if (showingLabels)
                imp.setSlice(current);
            return values;
        }

        private ImageStatistics getLineStatistics(Roi roi, ImageProcessor ip, int measurements, Calibration cal)
        {
            ImagePlus imp = new ImagePlus("", ip);
            imp.setRoi(roi);
            ProfilePlot profile = new ProfilePlot(imp);
            double[] values = profile.getProfile();
            ImageProcessor ip2 = new FloatProcessor(values.Length, 1, values);
            return ImageStatistics.getStatistics(ip2, measurements, cal);
        }

    }

}
